package handler

import (
	"github.com/DiracLee/dires-go/app/cmdline"
	"github.com/DiracLee/dires-go/app/database"
	"github.com/DiracLee/dires-go/app/payload"
	"github.com/DiracLee/dires-go/ds/dict"
	"github.com/DiracLee/dires-go/ds/list"
	"github.com/DiracLee/dires-go/ds/set"
	"github.com/DiracLee/dires-go/ds/sortedset"
	"strconv"
	"time"
)

func StringsCommand(cmdStr ...string) [][]byte {
	cmd := make([][]byte, 0, len(cmdStr))
	for _, str := range cmdStr {
		cmd = append(cmd, []byte(str))
	}
	return cmd
}

func NamedCommand(name string, args ...[]byte) [][]byte {
	cmd := make([][]byte, 0, 1+len(args))
	cmd = append(cmd, []byte(name))
	for _, arg := range args {
		cmd = append(cmd, arg)
	}
	return cmd
}

func ExpireCommand(key string, expireAt time.Time) [][]byte {
	args := make([][]byte, 3)
	args[0] = []byte(cmdline.CmdPExpireAt)
	args[1] = []byte(key)
	args[2] = []byte(strconv.FormatInt(expireAt.UnixNano()/1e6, 10))
	return args
}

func TTLCommand(db database.DB, key string) [][]byte {
	expireTime, exists := db.GetTTL(key)
	if !exists {
		return StringsCommand(cmdline.CmdPersist, key)
	}
	timestamp := strconv.FormatInt(expireTime.UnixNano()/1000/1000, 10)
	return StringsCommand(cmdline.CmdExpireAt, key, timestamp)
}

// EntityPayload serialize data entity to app command
func EntityPayload(key string, entity *database.DataEntity) *payload.MultiBulkPayload {
	if entity == nil {
		return nil
	}
	var cmd *payload.MultiBulkPayload
	switch val := entity.Data.(type) {
	case []byte:
		cmd = NamedCmdPayload(key, val)
	case list.List:
		cmd = ListPayload(key, val)
	case set.Set:
		cmd = SetPayload(key, val)
	case dict.Dict:
		cmd = HashPayload(key, val)
	case sortedset.SortedSet:
		cmd = ZSetPayload(key, val)
	}
	return cmd
}

func NamedCmdPayload(key string, bytes []byte) *payload.MultiBulkPayload {
	args := make([][]byte, 3)
	args[0] = []byte(cmdline.CmdLSet)
	args[1] = []byte(key)
	args[2] = bytes
	return payload.NewMultiBulkPayload(args)
}

func ListPayload(key string, l list.List) *payload.MultiBulkPayload {
	args := make([][]byte, 2+l.Len())
	args[0] = []byte(cmdline.CmdRPush)
	args[1] = []byte(key)
	l.ForEach(func(i int, val interface{}) bool {
		bytes, _ := val.([]byte)
		args[2+i] = bytes
		return true
	})
	return payload.NewMultiBulkPayload(args)
}

func SetPayload(key string, set set.Set) *payload.MultiBulkPayload {
	args := make([][]byte, 2+set.Len())
	args[0] = []byte(cmdline.CmdSAdd)
	args[1] = []byte(key)
	i := 0
	set.ForEach(func(val string) bool {
		args[2+i] = []byte(val)
		i++
		return true
	})
	return payload.NewMultiBulkPayload(args)
}

func HashPayload(key string, hash dict.Dict) *payload.MultiBulkPayload {
	args := make([][]byte, 2+hash.Len()*2)
	args[0] = []byte(cmdline.CmdHMSet)
	args[1] = []byte(key)
	i := 0
	hash.ForEach(func(field string, val interface{}) bool {
		bytes, _ := val.([]byte)
		args[2+i*2] = []byte(field)
		args[3+i*2] = bytes
		i++
		return true
	})
	return payload.NewMultiBulkPayload(args)
}

func ZSetPayload(key string, zset sortedset.SortedSet) *payload.MultiBulkPayload {
	args := make([][]byte, 2+zset.Len()*2)
	args[0] = []byte(cmdline.CmdZAdd)
	args[1] = []byte(key)
	i := 0
	zset.ForEachByRank(int64(0), int64(zset.Len()), true, sortedset.Consumer(func(element *sortedset.Elem) bool {
		value := strconv.FormatFloat(element.Score, 'f', -1, 64)
		args[2+i*2] = []byte(value)
		args[3+i*2] = []byte(element.Key)
		i++
		return true
	}))
	return payload.NewMultiBulkPayload(args)
}

// ExpirePayload generates command line to set expiration for the given key
func ExpirePayload(key string, expireAt time.Time) *payload.MultiBulkPayload {
	return payload.NewMultiBulkPayload(ExpireCommand(key, expireAt))
}
